home *** CD-ROM | disk | FTP | other *** search
/ NeXT Enterprise Objects Framework 1.1 / NeXT Enterprise Objects Framework 1.1.iso / NextDeveloper / Examples / EnterpriseObjects / FlatFileDataSource / TableDataSource.m < prev    next >
Encoding:
Text File  |  1995-02-17  |  20.9 KB  |  664 lines

  1. /* TableDataSource.m
  2.  *
  3.  *  This data source reads a flat file table (such as Product.table or Item.table) 
  4.  *  and generates EOGeneric records that can be passed to an EOController.
  5.  *
  6.  * You may freely copy, distribute, and reuse the code in this example.
  7.  * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
  8.  * fitness for any particular use.
  9.  *
  10.  *
  11.  *
  12.  */
  13.  
  14. #import <appkit/appkit.h>
  15. #import <eoaccess/eoaccess.h>
  16. #import "TableDataSource.h"
  17. #import "TableDataSourcePrivate.h"
  18. #import "DetailTableDataSource.h"
  19. #include <sys/file.h> 
  20.  
  21.  
  22. #define @QUALIFIER_ALL @"*"
  23. #define @QPROPERTY @"PROPERTY"
  24. #define @QVALUE @"VALUE"
  25.  
  26. @implementation TableDataSource
  27.  
  28. // init a new TableDataSource with an array of EOGeneric records and the path to
  29. // the table file (the flat file database!). The entity name will be found from a
  30. // record in the array.
  31. // Use this initialization method when you have existing records (possibly from
  32. // a real database) and you want to save them to a file
  33. - initWithEOGenericRecords:(NSArray *)records tablePath:(NSString *)tablePath {
  34.     NSString *tableFile;
  35.     NSMutableArray *dictArray = [NSMutableArray array];
  36.     NSDictionary *dict;
  37.     int i;
  38.  
  39.     // We need at least a record to get the entity
  40.     if (![records count]) {
  41.         NSLog(@"<initWithEOGenericRecords:tablePath:> No records");
  42.         return nil;
  43.     }
  44.     entity = [[records objectAtIndex:0] entity];
  45.     tableFile=[NSString stringWithFormat:@"%@/%@.table", tablePath, [entity name]];
  46.     for (i=0; i<[records count]; i++) {
  47.         dict = [[records objectAtIndex:i] valuesForKeys:[entity classPropertyNames]];
  48.         [dictArray addObject:dict];
  49.     }
  50.     
  51.     // Save the records to a file as a property-list string
  52.     if (![[[dictArray description] dataUsingEncoding:NSASCIIStringEncoding] 
  53.             writeToFile:tableFile atomically:NO]) {
  54.         NSLog(@"<initWithEOGenericRecords:tablePath:> DataSource %@ failed to save", [entity name]);
  55.         return nil;
  56.     }
  57.     // Call the designated initialization method
  58.     else return [self initWithEntity:entity tablePath:tablePath];
  59.     
  60. }
  61.  
  62. // The designated initialization method for TableDataSource
  63. // Pick an entity in the model, then initialize the source from a flat file that
  64. // contains a string representation of an array of dictionaries.
  65. // Note that unlike the EODatabaseSataSource, this dataSource has a "state".
  66. // It cashes the eos (dictionaries) that it read from the file and also maintains 
  67. // a few hash tables to speed up fetch operations...
  68. // Although the state of the TableDataSource is an array of dictionaries (I call it
  69. // the snapshot) the TabledataSource will copy these dictionaries into an array of 
  70. // EOGeneric records before handing them to a controller...
  71. - initWithEntity:(EOEntity *)anEntity tablePath:(NSString *)tablePath {
  72.     NSString *primaryKey;
  73.     NSArray *relations;
  74.     NSMutableArray *qualifiers = [NSMutableArray array];
  75.  
  76.     [super init];
  77.     entity = [anEntity retain];
  78.     primaryKey = [(EOAttribute *)[[entity primaryKeyAttributes] objectAtIndex:0] name];
  79.     relations = [entity relationships];
  80.     detailSources = [[NSMutableArray array] retain]; 
  81.     return [self initWithTable:tablePath primaryKey:primaryKey qualifierKeys:qualifiers];
  82. }
  83.  
  84. // The new NSObject world, I guess
  85. - (void)dealloc {
  86.     [table autorelease];
  87.     [eos autorelease];
  88.     [lookupTables autorelease];
  89.     [qualifier autorelease];
  90.     [uniqueKey autorelease];
  91.     [detailSources autorelease];
  92.     [entity autorelease];
  93.     [orderByKey autorelease];
  94.     [super dealloc];
  95. }
  96.  
  97. // The path to persistent storage, here to the "table" file... 
  98. - (NSString *)tablePath {
  99.     return table;
  100. }
  101.  
  102. // The other one...
  103. - setTablePath:(NSString *)aPath {
  104.     [table autorelease];
  105.     table = [aPath retain];
  106.     return self;
  107. }
  108.  
  109. // The supported entity, we got it from the EOmodel at initialization time
  110. - (EOEntity *)entity {
  111.     return entity;
  112. }
  113.  
  114. // A custom way (meaning different from the EOF way) to qualify the source for a given property name and value
  115. // The equivalent SQL is: WHERE( key = value)
  116. - qualifyForProperty:(NSString *)key andValue:value {
  117.     NSMutableDictionary *qual = [NSMutableDictionary dictionary];
  118.     
  119.     if (![[self keys] containsObject:key]) {
  120.         NSLog(@"%@ is not a valid property of entity %@", key, [entity name]);
  121.         qual=nil;
  122.     }
  123.     else {
  124.         [qual setObject:key forKey:@QPROPERTY];
  125.         [qual setObject:value forKey:@QVALUE];
  126.     }
  127.     return [self setQualifier:qual];
  128. }
  129.  
  130. // Qualify for all records
  131. - setEntityQualifier {
  132.     NSMutableDictionary *qual =[NSMutableDictionary dictionary];
  133.     [qual setObject:uniqueKey forKey:@QPROPERTY];
  134.     [qual setObject:@QUALIFIER_ALL forKey:@QVALUE];
  135.     return [self setQualifier:qual];
  136. }
  137.  
  138. // Set the qualifier before the fetch
  139. - setQualifier:(NSMutableDictionary *)newQualifier {
  140.     [qualifier autorelease];
  141.     if (newQualifier)  qualifier  = [newQualifier retain];
  142.     else qualifier=nil;
  143.     //NSLog(@"New qualifier %@", [(NSMutableDictionary *)qualifier description]);
  144.     return self;
  145. }
  146.  
  147. - setEmptySetQualifier {
  148.     [qualifier autorelease];
  149.     qualifier=nil;
  150.     return self;
  151. }
  152.  
  153. - (BOOL)orderBy:(NSString *)key {
  154.     if (![[self keys] containsObject:key]) return NO;
  155.     else {
  156.         [orderByKey autorelease];
  157.         orderByKey = [key retain];
  158.         [eos sortUsingFunction:eoSort context:orderByKey];
  159.         return YES;
  160.     }
  161. }
  162.  
  163. - setOrderDescendantSources:(BOOL)aFlag {
  164.     orderDescendantSources=aFlag;
  165.     return self;
  166. }
  167.  
  168. // *************************************  DATASOURCE PROTOCOL ****************/
  169. // The class property names (keys for the dictionary or EOGenericRecord)
  170. - (NSArray *)keys {
  171.     NSArray *attrs = [entity attributes];
  172.     NSMutableArray *keys = [NSMutableArray array];
  173.     int i;
  174.  
  175.     for (i=0; i<[attrs count]; i++) {
  176.         EOAttribute *at = [attrs objectAtIndex:i];
  177.         [keys addObject:[at name]];
  178.     }
  179.     return keys;
  180. }
  181.  
  182. - createObject{
  183.     NSNumber *newId=[self findNextPrimaryKey];
  184.     NSMutableDictionary *newObject=[[NSMutableDictionary alloc] init];
  185.     
  186.     if (!newId) return nil;
  187.     else {
  188.         NSArray *allKeys = [self keys];
  189.         NSArray *relations = [entity relationships];
  190.         int i, count;
  191.  
  192.         for (i=0, count=[allKeys count]; i<count; i++) {
  193.             id key = [allKeys objectAtIndex:i];
  194.             [newObject setObject:@"" forKey:key];
  195.         }
  196.         for (i=0, count=[relations count]; i<count; i++) {
  197.             EORelationship *rel = [relations objectAtIndex:i];
  198.             if ([rel isToMany]) [newObject setObject:[newId description] forKey:[rel name]];
  199.         }
  200.         [newObject setObject:[newId description] forKey:uniqueKey];
  201.         return newObject;
  202.     }
  203. }
  204.  
  205. - (BOOL)insertObject:object {
  206.     NSNumber *objectId=[object objectForKey:uniqueKey];
  207.     NSArray *allKeys;
  208.     int i;
  209.     NSMutableDictionary *newObject;
  210.  
  211.     if (![self isValidNewPrimaryKey:objectId]) {
  212.         NSLog(@"DataSource cannot insert; primary key is invalid: %@", [(NSString *)objectId description]);
  213.         return NO;
  214.     }
  215.  
  216.     newObject=[NSMutableDictionary dictionary];
  217.     allKeys = [self keys];
  218.     for (i=0; i<[allKeys count]; i++) {
  219.         id key = [allKeys objectAtIndex:i];
  220.         id value = [object objectForKey:key];
  221.         if ([value isEqual:[EONull null]]) value=@"";
  222.         [newObject setObject:value forKey:key];
  223.     }
  224.     
  225.     [eos addObject:newObject];
  226.     if (orderByKey) [eos sortUsingFunction:eoSort context:orderByKey];
  227.     [self modifyLookupTables];
  228.     return YES;
  229. }
  230.  
  231. - (BOOL)deleteAllObjects {
  232.     int i;
  233.     for (i=[eos count]-1; i>=0; i--) {
  234.         [eos removeObject:[eos objectAtIndex:i]];
  235.     }
  236.     [self modifyLookupTables];
  237.     return YES;
  238. }
  239.  
  240. - (BOOL)deleteObject:object {
  241.     id primaryKeyValue = [object objectForKey:uniqueKey];
  242.     NSMutableArray *deletes=[[lookupTables objectForKey:uniqueKey] objectForKey:primaryKeyValue];
  243.     int i;
  244.  
  245.     if ( !deletes || ![deletes count]) return NO;
  246.     for (i=0; i<[deletes count]; i++) {
  247.         [eos removeObject:[deletes objectAtIndex:i]];
  248.     }
  249.     [self modifyLookupTables];
  250.     return YES;
  251. }
  252.  
  253. - (BOOL)updateObject:object {
  254.     id primaryKeyValue = [object objectForKey:uniqueKey];
  255.     NSMutableArray *update=[[lookupTables objectForKey:uniqueKey] objectForKey:primaryKeyValue];
  256.     NSArray *allKeys;
  257.     NSMutableDictionary *eo;
  258.     int i;
  259.     NSString *key;
  260.     id value;
  261.  
  262.     if (!update || [update count]!=1) return NO;
  263.     allKeys = [self keys];
  264.     eo = [update objectAtIndex:0];
  265.     for (i=0; i<[allKeys count]; i++) {
  266.         key = [allKeys objectAtIndex:i];
  267.         value = [object objectForKey:key];
  268.         if (!value || [value isEqual:[EONull null]] ) value=@"";
  269.         [eo setObject:value forKey:key];
  270.     }
  271.     if (orderByKey) [eos sortUsingFunction:eoSort context:orderByKey];
  272.     [self modifyLookupTables];
  273.     return YES;
  274. }
  275.  
  276. - (NSArray *)fetchObjects {
  277.     NSArray *records;
  278.     NSString *qualifierPropertyKey = [qualifier objectForKey:@QPROPERTY];
  279.     NSString *qualifierPropertyValue = [qualifier objectForKey:@QVALUE];
  280.  
  281.     if (qualifierPropertyKey && qualifierPropertyValue) {
  282.  
  283.         if ( [qualifierPropertyValue isEqual:@QUALIFIER_ALL] ) {
  284.             records =[self eosArrayToGenericRecordsArray:eos];
  285.         }
  286.         else {
  287.             NSMutableDictionary *hash = [lookupTables objectForKey:qualifierPropertyKey];
  288.             if (!hash) {
  289.                 NSLog(@"No lookup table for qualifier key %@", qualifierPropertyKey);
  290.                 records =nil;
  291.             }
  292.             else {
  293.                 NSArray *qualifiedEos=[hash objectForKey:qualifierPropertyValue];
  294.                 if (!qualifiedEos || ![qualifiedEos count]) {
  295.                     //NSLog(@"No qualified eos where %@=%@", qualifierPropertyKey, [(NSString *)qualifierPropertyValue description]);
  296.                     records =nil;
  297.                 }
  298.                 records =[self eosArrayToGenericRecordsArray:qualifiedEos];
  299.             }
  300.  
  301.         }
  302.     }
  303.     else {
  304.         //NSLog(@"nil or uncomplete qualifier %@", [(NSMutableDictionary *)qualifier description]);
  305.         records = [NSArray array]; 
  306.     }
  307.     //NSLog(@"TABLE %@ - FETCHED OBJECTS: %d", [entity name], [records count]);
  308.     return records;
  309. }
  310.  
  311. // This is where we save the eos to persistent storage (COMMIT in SQL)
  312. - (BOOL)saveObjects{
  313.     NSString *tableFile=[NSString stringWithFormat:@"%@/%@.table", table, [entity name]];
  314.     if (![[[eos description] dataUsingEncoding:NSASCIIStringEncoding] 
  315.             writeToFile:tableFile atomically:NO]) {
  316.         NSString *emess;
  317.  
  318.         if ( (!access([tableFile cString], F_OK)) && (access([tableFile cString], W_OK)) ) {
  319.             int ans;
  320.             emess = [NSString stringWithFormat:@"\'%@\' data source cannot save changes. You do not have write permission on \'%@\'. Do you want to overwrite?", [entity name], tableFile];
  321.             ans=NXRunAlertPanel("ERROR", [emess cString], "Overwrite", "Cancel", NULL);
  322.             if (ans==NX_ALERTDEFAULT) return [self forceSaveObjects];
  323.             else return NO;
  324.         }
  325.         else return NO;
  326.     }
  327.     else return YES;
  328. }
  329.  
  330. - (BOOL)canDelete {
  331.     return YES;
  332. }
  333.  
  334. // Not implemented yet...
  335. // Coerce a value to the appropriate type.
  336. // This method should convert to either an NSNumber, NSString, NSData,
  337. // a custom type, or nil.  The value return by this method may be safely
  338. // passed to an EO via takeValuesFromDictionary:.  This method is used
  339. // by controllers to coerce values supplied from associations before
  340. // those values are passed on to the EOs.
  341. - coerceValue: value forKey: (NSString *)key {
  342.     // sorry, another day , maybe...
  343.     return value;
  344. }
  345.  
  346. // ******************************  MASTER DATASOURCE PROTOCOL ****************/
  347. // What we are doing here is closer to a master-peer than a master-detail setup.
  348. // We are creating the detail dataSource and handing it to the association. 
  349. // This new detail dataSource will be attached to the detail controller automatically
  350. // From there, the detail controller and its DetailTableDataSource will be on their own
  351. // Also notice than in an EOF master-detail setup, a master eo (meaning an eo 
  352. // produced by the master source) "carries" its detail eos. In other word if one 
  353. // writes [masterEo objectForKey:masterDetailRelationshipKey], the master eo will 
  354. // return an array full of detail eos (assuming that the master detail relationship
  355. // was set as a class property name in EOModeler). Although it could, the TableDataSource 
  356. // does not support that feature...
  357. - (id <EOQualifiableDataSources>)dataSourceQualifiedByKey:(NSString *)key {
  358.     EORelationship *rel = [entity relationshipNamed:key];
  359.     EOEntity *detailEntity=[rel destinationEntity];
  360.     DetailTableDataSource *detailSource;
  361.  
  362.     if (!rel) return nil;
  363.     if (!detailEntity) return nil;
  364.     detailSource = [[(DetailTableDataSource *)[DetailTableDataSource alloc] initWithMasterDataSource:self entity:detailEntity] autorelease];    
  365.     if (orderByKey && orderDescendantSources) [detailSource orderBy:orderByKey];
  366.  
  367.     // We cash it (I had some grandiose plane, unused today!!)
  368.     [detailSources addObject:detailSource];
  369.     return detailSource;
  370. }
  371.  
  372. // ***************************************************************************/
  373.  
  374. // a convenience method to fetch all the objects in the entity
  375. - (NSArray *)fetchAllObjects {
  376.     NSMutableDictionary *qual =[NSMutableDictionary dictionary];
  377.     [qual setObject:uniqueKey forKey:@QPROPERTY];
  378.     [qual setObject:@QUALIFIER_ALL forKey:@QVALUE];
  379.     [self setQualifier:qual];
  380.     return [self fetchObjects];
  381. }
  382.  
  383. // a convenience fetch method
  384. - objectForPrimaryKey:value {
  385.     id hash = [lookupTables objectForKey:uniqueKey];
  386.     NSArray *objects = [hash objectForKey:value];
  387.     id object;
  388.     EOGenericRecord *record;
  389.     NSMutableDictionary *primaryKeyDictionary;
  390.     NSString *key;
  391.     id copy;
  392.     int count, j;
  393.     NSArray *allKeys = [self keys];
  394.  
  395.     if ([objects count]!=1) return nil;
  396.     object = [objects objectAtIndex:0];
  397.     primaryKeyDictionary=[NSMutableDictionary dictionary];
  398.     [primaryKeyDictionary setObject:value forKey:uniqueKey];
  399.     record = [[EOGenericRecord alloc] initWithPrimaryKey:primaryKeyDictionary         
  400.                                         entity:entity];
  401.     for (j=0, count=[allKeys count]; j<count; j++) {
  402.         key = [allKeys objectAtIndex:j];
  403.         copy = [[object objectForKey:key] copy];
  404.         [record setObject:[copy autorelease] forKey:key];
  405.     }
  406.     return [record autorelease];
  407. }
  408.  
  409. - addLookupTableForKey:(NSString *)key {
  410.     NSMutableDictionary *hash;
  411.  
  412.     hash = [self createLookupTableForQualifierKey:key];
  413.     [lookupTables setObject:hash forKey:key];
  414.     return self;
  415. }
  416.  
  417.  
  418.  
  419. @end
  420.  
  421. // *************************************  Private Category ****************/
  422. @implementation TableDataSource (Private)
  423.  
  424. // This initialization method is called by initWithEntity:tablePath:
  425. // It sets the key for the primary key  and creates a key->array of objects hash
  426. // tables for each element of the qualifiers array.
  427. - initWithTable:(NSString *)tablePath primaryKey:(NSString *)primaryKey qualifierKeys:(NSArray *)qualifiers {
  428.  
  429.     // Create snapshot from table (eos)
  430.     table = [tablePath retain];
  431.     if (![self createSnapshot]) return nil;
  432.  
  433.     // Create a dictionary of hash tables for query (key is like @"ID", value is a lookup table)
  434.     uniqueKey = [primaryKey retain];
  435.     if ( !([self createLookupTables:qualifiers]) ) return nil;
  436.  
  437.     // set qualifier to select everything 
  438.     // To be clean, we should have a qualifier Class, oh well...it is a rush job
  439.     // after all
  440.     orderByKey=nil;
  441.     orderDescendantSources = NO;
  442.     qualifier = [[NSMutableDictionary alloc] init];
  443.     [qualifier setObject:uniqueKey forKey:@QPROPERTY];
  444.     [qualifier setObject:@QUALIFIER_ALL forKey:@QVALUE];
  445.  
  446.     return self;
  447. }
  448.  
  449. // The flat file is read here and the objects that have been retrieved are cashed
  450. // in an instance variable (of class NSMutableArray) called eos
  451. - createSnapshot {
  452.     NSString *tableFile=[NSString stringWithFormat:@"%@/%@", table, [entity externalName]];
  453.     NSString *tableString;
  454.     NSArray  *nonMutableEos;
  455.  
  456.     // read the table file that should be in a property list format    
  457.     // Isn't foundation great or what?!!!
  458.     if ( !(tableString=[[NSString alloc] initWithContentsOfFile:tableFile]) ||
  459.          !(nonMutableEos = [tableString propertyList]) ) {
  460.             NSLog(@"<createSnapshot> Cannot init from table %@", tableFile);
  461.             return nil;
  462.     }
  463.     // Make deep mutable copy
  464.     else {
  465.         NSEnumerator *eoEnumerator = [nonMutableEos objectEnumerator];
  466.         NSDictionary *nonMutableEo;
  467.  
  468.         eos = [[NSMutableArray alloc] init];
  469.         while ( nonMutableEo = [eoEnumerator nextObject] ) {
  470.             [eos addObject: [[nonMutableEo mutableCopy] autorelease]];
  471.         }
  472.     }
  473.     return self;
  474. }
  475.  
  476. // Create the lookup tables (to speed up fetch operations)
  477. // The qualifiers array contains the property names that we should hash on
  478. // a lookup table is in the form key = "a property key" --> value = an array of 
  479. // dictionaries...
  480. - createLookupTables:(NSArray *)qualifiers {
  481.     int i, count;
  482.     NSMutableDictionary *hash;
  483.     NSString *qualifierKey;
  484.  
  485.     lookupTables = [[NSMutableDictionary alloc] init];
  486.     hash = [self createLookupTableForQualifierKey:uniqueKey];
  487.     [lookupTables setObject:hash forKey:uniqueKey];
  488.  
  489.     for (i=0, count=[qualifiers count]; i<count; i++) {
  490.     
  491.         qualifierKey = [qualifiers objectAtIndex:i];
  492.         if ( ![lookupTables objectForKey:qualifierKey] ) {
  493.             //NSLog(@"Creating lookup table for qualifierKey %@", qualifierKey);
  494.             if ( !(hash = [self createLookupTableForQualifierKey:qualifierKey]) ) return nil;
  495.             [lookupTables setObject:hash forKey:qualifierKey];
  496.         }
  497.     }
  498.     return self;
  499. }
  500.  
  501. // Create a lookup table for a given property name (the key)
  502. - createLookupTableForQualifierKey:(NSString *)key {
  503.  
  504.     NSMutableDictionary *hash;
  505.     int i, count;
  506.     NSDictionary *eo;
  507.     NSMutableArray *values;
  508.     id value;
  509.     
  510.      hash=[NSMutableDictionary dictionary];
  511.  
  512.     for (i=0, count=[eos count]; i<count; i++) {
  513.  
  514.         eo = [eos objectAtIndex:i];
  515.  
  516.         // get the property value for the qualifier key
  517.         if ( !(value = [eo objectForKey:key]) ) {
  518.             NSLog(@"<createLookupTableForQualifierKey:> eos dictionary does not respond to qualifier key %@", key);
  519.             return nil;
  520.         }
  521.     
  522.         // It is the first time that we encounter an eo such that propertyValue(key) = value
  523.         if ( !(values = [hash objectForKey:value]) ) {
  524.             values = [NSMutableArray array];
  525.             [values addObject:eo];
  526.             [hash setObject:values forKey:[(NSObject *)value description]];
  527.         }
  528.         // We have already found an eo such that propertyValue(key) = value
  529.         else {
  530.             [values addObject:eo];
  531.         }
  532.     }
  533.     //NSLog(@"HASH FOR KEY %@\n%@", key, [(NSDictionary *)hash description]);
  534.     return hash;
  535. }
  536.  
  537. // After insert, delete or update opeations, the lookup tables must be refreshed
  538. - modifyLookupTables {
  539.     NSArray *allKeys=[lookupTables allKeys];
  540.     int i, count;
  541.     NSString *key;
  542.     NSMutableDictionary *hash;
  543.  
  544.     for (i=0, count=[allKeys count]; i<count; i++) {
  545.         key = [allKeys objectAtIndex:i];
  546.         [lookupTables removeObjectForKey:key];
  547.         hash = [self createLookupTableForQualifierKey:key];
  548.         [lookupTables setObject:hash forKey:key];
  549.     }
  550.     return self;
  551. }
  552.  
  553. // a method to transform an array of eos (state of the TableDataSource) into
  554. // an array of EOGeneric records that we can hand to the outside worls...
  555. - (NSMutableArray *)eosArrayToGenericRecordsArray:(NSArray *)eoArray {
  556.     NSMutableArray *records = [NSMutableArray  array];
  557.     int i, eoCount;
  558.  
  559.     for (i=0, eoCount=[eoArray count]; i<eoCount; i++) {
  560.         NSMutableDictionary *eo;
  561.         NSMutableDictionary *primaryKey=[NSMutableDictionary dictionary];
  562.         EOGenericRecord *record;
  563.         NSArray *allKeys;
  564.         NSArray *relations;
  565.         int j, count;
  566.  
  567.         eo = [eoArray objectAtIndex:i];
  568.         allKeys = [self keys];
  569.         [primaryKey setObject:[eo objectForKey:uniqueKey] forKey:uniqueKey];
  570.         record = [[[EOGenericRecord alloc] initWithPrimaryKey:primaryKey entity:entity] autorelease];
  571.         for (j=0, count=[allKeys count]; j<count; j++) {
  572.             NSString *key = [allKeys objectAtIndex:j];
  573.             id copy;
  574.  
  575.             copy = [[eo objectForKey:key] copy];
  576.             [record setObject:[copy autorelease] forKey:key];
  577.         }
  578.         relations = [entity relationships];
  579.  
  580.         // We support master-details but not flattened attribute for now!!!
  581.         for (j=0, count=[relations count]; j<count; j++) {
  582.             EORelationship *rel = [relations objectAtIndex:j];
  583.             id copy;
  584.     
  585.             if ([rel isToMany]) {
  586.                 copy = [[eo objectForKey:uniqueKey] copy];
  587.                 [record setObject:[copy autorelease] forKey:[rel name]];
  588.             }
  589.  
  590.         }
  591.         [records addObject:record];
  592.     }
  593.     return records;
  594. }
  595.  
  596. // Unlike the EODatabaseDataSource, when a TableDataSource creates a new object
  597. // It sets the primary key value for the created object. 
  598. // This method computes the next available primary key (the next in the sequence)
  599. - (NSNumber *)findNextPrimaryKey {
  600.     int i, max=-1;
  601.  
  602.     for (i=0; i<[eos count]; i++) {
  603.         id eo;
  604.         int current;
  605.  
  606.         eo = [eos objectAtIndex:i];
  607.         current = [[eo objectForKey:uniqueKey] intValue];
  608.         if (current  > max) max=current;
  609.     }
  610.     return [NSNumber numberWithInt:max+1];
  611. }
  612.  
  613. // make sure the the primary key of a new object handed for insertion has a valid primary key
  614. // (in this case, valid means not used by a record stored in the table)
  615. - (BOOL)isValidNewPrimaryKey:(NSNumber *)number {
  616.     int i;
  617.  
  618.     for (i=0; i<[eos count]; i++) {
  619.         id eo;
  620.  
  621.         eo = [eos objectAtIndex:i];
  622.         if ([number isEqual:[eo objectForKey:uniqueKey]]) return NO;
  623.     }
  624.     return YES;
  625. }
  626.  
  627. - (BOOL)forceSaveObjects {
  628.     NSString *tableFile=[NSString stringWithFormat:@"%@/%@.table", table, [entity name]];
  629.     NSString *emess=@"Cannot overwrite; changes will not be saved...";
  630.     NSString *cmd;
  631.  
  632.     cmd = [NSString stringWithFormat:@"mv %@ %@.otto", tableFile, tableFile];
  633.     if (system([cmd cString])) {
  634.         NXRunAlertPanel("ERROR", [emess cString], "OK", NULL, NULL);
  635.         return NO;
  636.     }
  637.     cmd = [NSString stringWithFormat:@"cp %@.otto %@; ", tableFile, tableFile];
  638.     if (system([cmd cString])) {
  639.         NXRunAlertPanel("ERROR", [emess cString], "OK", NULL, NULL);
  640.         return NO;
  641.     }
  642.     cmd = [NSString stringWithFormat:@"rm -f %@.otto; chmod u+w %@", tableFile, tableFile];
  643.     system([cmd cString]);
  644.  
  645.     if (![[[eos description] dataUsingEncoding:NSASCIIStringEncoding] 
  646.             writeToFile:tableFile atomically:NO]) return NO;
  647.     else return YES;
  648.     
  649. }
  650.  
  651. int eoSort(id eo1, id eo2, void *context) {
  652.     NSString *key=(NSString *)context;
  653.     NSString *val1, *val2;
  654.  
  655.     if (!key) return NSOrderedSame;
  656.     val1 = [eo1 objectForKey:key];
  657.     val2 = [eo2 objectForKey:key];
  658.     if (!val1 || !val2) return NSOrderedSame;
  659.     else return [val1 compare:val2];
  660. }
  661.  
  662.  
  663. @end
  664.